import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../leak/model/leak_info.dart';
import '../leak_stream_watcher.dart';
import '../task/leak_task.dart';
import '../task/local_save_leak.dart';
import '../utils/log_util.dart';

const int defaultCheckLeakDelay = 15;

typedef ShouldAddedRoute = bool Function(Route route);

class LeakObserver extends NavigatorObserver {
  final ShouldAddedRoute? shouldCheck;
  final int checkLeakDelay;
  final bool needLog;
  final bool enableWatch;
  final Function localSaveFilePath;
  final GlobalKey<NavigatorState> navigatorKey;

  LeakObserver({
    required this.navigatorKey,
    required this.localSaveFilePath,
    this.checkLeakDelay = defaultCheckLeakDelay,
    this.shouldCheck,
    this.needLog = true,
    this.enableWatch = true,
  }) {
    _init();
  }

  Future<void> _init() async {
    if (kDebugMode && enableWatch) {
      LogUtil.init(needLog: needLog, title: "LeakLog");
      localSavaLeak.savePath = await localSaveFilePath.call();
      LeakStream.getInstance().stream.listen((node) {
        LogUtil.d("泄漏信息: \n${node.toString()}");
        final leakInfo = LeakInfo(DateTime.now().toString().substring(0, 19), node, 1);
        localSavaLeak.insert(leakInfo);
      });
    }
  }

  @override
  void didPop(Route route, Route? previousRoute) {
    if (kDebugMode) _remove(route);
  }

  @override
  void didPush(Route route, Route? previousRoute) {
    if (kDebugMode) _add(route);
  }

  @override
  void didRemove(Route route, Route? previousRoute) {
    if (kDebugMode) _remove(route);
  }

  @override
  void didReplace({Route? newRoute, Route? oldRoute}) {
    if (kDebugMode) {
      if (newRoute != null) {
        _add(newRoute);
      }
      if (oldRoute != null) {
        _remove(oldRoute);
      }
    }
  }

  final Map<String, Expando> _widgetRefMap = {};
  final Map<String, Expando> _stateRefMap = {};

  void _add(Route route) {
    route.didPush().then((value) {
      Element? element = _getElementByRoute(route);
      if (element != null) {
        Expando expando = Expando('${element.widget}');
        expando[element.widget] = true;
        _widgetRefMap[_generateKey(route)] = expando;
        if (element is StatefulElement) {
          Expando expandoState = Expando('${element.state}');
          expando[element.state] = true;
          _stateRefMap[_generateKey(route)] = expandoState;
        }
      }
    });
  }

  ///check and analyze the route
  void _remove(Route route) {
    Element? element = _getElementByRoute(route);
    if (element != null) {
      LogUtil.d("${element.widget}已退出，${checkLeakDelay}s后开始检测是否有泄露...");
      Future.delayed(Duration(seconds: checkLeakDelay), () {
        LeakTask(_widgetRefMap.remove(_generateKey(route))).start(tag: "widget leaks");

        if (element is StatefulElement) {
          LeakTask(_stateRefMap.remove(_generateKey(route))).start(tag: "state leaks");
        }
      });
    }
  }

  String _generateKey(Route route) {
    return '${route.hashCode}-${route.runtimeType}';
  }

  Element? _getElementByRoute(Route route) {
    Element? element;
    if (route is ModalRoute && (shouldCheck == null || shouldCheck!.call(route))) {
      //RepaintBoundary
      route.subtreeContext?.visitChildElements((child) {
        //Builder
        child.visitChildElements((child) {
          //Semantics
          child.visitChildElements((child) {
            //My Page
            element = child;
          });
        });
      });
    }
    return element;
  }
}
